package org.apache.lucene.store;

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

/** A straightforward implementation of {@link FSDirectory}
 *  using java.io.RandomAccessFile.  However, this class has
 *  poor concurrent performance (multiple threads will
 *  bottleneck) as it synchronizes when multiple threads
 *  read from the same file.  It's usually better to use
 *  {@link NIOFSDirectory} or {@link MMapDirectory} instead. */
public class SimpleFSDirectory extends FSDirectory {

    /** Create a new SimpleFSDirectory for the named location.
     *
     * @param path the path of the directory
     * @param lockFactory the lock factory to use, or null for the default
     * ({@link NativeFSLockFactory});
     * @throws IOException
     */
    public SimpleFSDirectory(File path, LockFactory lockFactory) throws IOException {
        super(path, lockFactory);
    }

    /** Create a new SimpleFSDirectory for the named location and {@link NativeFSLockFactory}.
     *
     * @param path the path of the directory
     * @throws IOException
     */
    public SimpleFSDirectory(File path) throws IOException {
        super(path, null);
    }

    /** Creates an IndexInput for the file with the given name. */
    @Override
    public IndexInput openInput(String name, int bufferSize) throws IOException {
        ensureOpen();
        final File path = new File(directory, name);
        return new SimpleFSIndexInput("SimpleFSIndexInput(path=\"" + path.getPath() + "\")", path, bufferSize, getReadChunkSize());
    }

    protected static class SimpleFSIndexInput extends BufferedIndexInput {

        protected static class Descriptor extends RandomAccessFile {
            // remember if the file is open, so that we don't try to close it
            // more than once
            protected volatile boolean isOpen;
            long position;
            final long length;

            public Descriptor(File file, String mode) throws IOException {
                super(file, mode);
                isOpen = true;
                length = length();
            }

            @Override
            public void close() throws IOException {
                if (isOpen) {
                    isOpen = false;
                    super.close();
                }
            }
        }

        protected final Descriptor file;
        boolean isClone;
        //  LUCENE-1566 - maximum read length on a 32bit JVM to prevent incorrect OOM 
        protected final int chunkSize;

        /** @deprecated please pass resourceDesc */
        @Deprecated
        public SimpleFSIndexInput(File path, int bufferSize, int chunkSize) throws IOException {
            this("anonymous SimpleFSIndexInput", path, bufferSize, chunkSize);
        }

        public SimpleFSIndexInput(String resourceDesc, File path, int bufferSize, int chunkSize) throws IOException {
            super(resourceDesc, bufferSize);
            file = new Descriptor(path, "r");
            this.chunkSize = chunkSize;
        }

        /** IndexInput methods */
        @Override
        protected void readInternal(byte[] b, int offset, int len) throws IOException {
            synchronized (file) {
                long position = getFilePointer();
                if (position != file.position) {
                    file.seek(position);
                    file.position = position;
                }
                int total = 0;

                try {
                    do {
                        final int readLength;
                        if (total + chunkSize > len) {
                            readLength = len - total;
                        } else {
                            // LUCENE-1566 - work around JVM Bug by breaking very large reads into chunks
                            readLength = chunkSize;
                        }
                        final int i = file.read(b, offset + total, readLength);
                        if (i == -1) {
                            throw new IOException("read past EOF");
                        }
                        file.position += i;
                        total += i;
                    } while (total < len);
                } catch (OutOfMemoryError e) {
                    // propagate OOM up and add a hint for 32bit VM Users hitting the bug
                    // with a large chunk size in the fast path.
                    final OutOfMemoryError outOfMemoryError = new OutOfMemoryError("OutOfMemoryError likely caused by the Sun VM Bug described in " + "https://issues.apache.org/jira/browse/LUCENE-1566; try calling FSDirectory.setReadChunkSize " + "with a value smaller than the current chunk size (" + chunkSize + ")");
                    outOfMemoryError.initCause(e);
                    throw outOfMemoryError;
                } catch (IOException ioe) {
                    IOException newIOE = new IOException(ioe.getMessage() + ": " + this);
                    newIOE.initCause(ioe);
                    throw newIOE;
                }
            }
        }

        @Override
        public void close() throws IOException {
            // only close the file if this is not a clone
            if (!isClone)
                file.close();
        }

        @Override
        protected void seekInternal(long position) {
        }

        @Override
        public long length() {
            return file.length;
        }

        @Override
        public Object clone() {
            SimpleFSIndexInput clone = (SimpleFSIndexInput) super.clone();
            clone.isClone = true;
            return clone;
        }

        /** Method used for testing. Returns true if the underlying
         *  file descriptor is valid.
         */
        boolean isFDValid() throws IOException {
            return file.getFD().valid();
        }

        @Override
        public void copyBytes(IndexOutput out, long numBytes) throws IOException {
            numBytes -= flushBuffer(out, numBytes);
            // If out is FSIndexOutput, the copy will be optimized
            out.copyBytes(this, numBytes);
        }
    }
}
